在微服務架構中,一個使用者請求可能會流經數十個獨立的服務。當發生延遲或錯誤時,要找出問題的根源就像大海撈針。這就是「分散式追蹤 (Distributed Tracing)」發揮作用的地方。
今天,我們將入門分散式追蹤的世界,並學習如何使用 Grafana Tempo 這款專為追蹤而生的高效能後端系統。
分散式追蹤是一種用來監控和分析橫跨多個服務的請求流程的方法。它幫助我們視覺化一個請求的完整生命週期。

(圖片來源: Grafana Labs)
透過分析 Trace 中各個 Span 的耗時與關係,我們可以輕易地發現系統中的效能瓶頸。
Grafana Tempo 是一個開源、高擴展性、低成本的分散式追蹤後端。它的設計哲學是「簡單且海量」。
接下來,我們將使用 Docker Compose 建立一個完整的本地環境,包含:
/day24
├── app/
│   ├── main.py
│   └── requirements.txt
├── docker-compose.yml
├── tempo.yaml
├── otel-collector.yaml
└── grafana-datasource.yaml
首先,在 app/requirements.txt 中加入必要的套件:
# app/requirements.txt
fastapi
uvicorn
opentelemetry-api
opentelemetry-sdk
opentelemetry-exporter-otlp
opentelemetry-instrumentation-fastapi
然後,撰寫 app/main.py,並使用 OpenTelemetry 進行自動埋點。
# app/main.py
from fastapi import FastAPI
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
# 1. 設定服務名稱
resource = Resource(attributes={
    "service.name": "fastapi-demo-app"
})
# 2. 設定 Tracer Provider
trace.set_tracer_provider(TracerProvider(resource=resource))
tracer = trace.get_tracer(__name__)
# 3. 設定 OTLP Exporter,將數據發送到 OTEL Collector
otlp_exporter = OTLPSpanExporter(
    endpoint="otel-collector:4317",  # Collector 的 gRPC 端口
    insecure=True
)
# 4. 設定 Span Processor
span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)
# 建立並自動埋點 FastAPI App
app = FastAPI()
FastAPIInstrumentor.instrument_app(app)
@app.get("/")
def read_root():
    return {"message": "Hello, World!"}
@app.get("/items/{item_id}")
def read_item(item_id: int):
    # 可以在特定端點內建立自訂的 Span
    with tracer.start_as_current_span("process_item") as span:
        span.set_attribute("item.id", item_id)
        # 模擬一些工作
        import time
        time.sleep(0.1)
        return {"item_id": item_id}
otel-collector.yaml: 設定 Collector 接收 OTLP 數據,並將其匯出到 Tempo。
receivers:
  otlp:
    protocols:
      grpc:
      http:
processors:
  batch:
exporters:
  otlp:
    endpoint: "tempo:4317" # Tempo 的 gRPC 端口
    tls:
      insecure: true
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp]
tempo.yaml: 設定 Tempo 接收 OTLP 數據,並將追蹤儲存在本地檔案系統。
server:
  http_listen_port: 3200
distributor:
  receivers:
    otlp:
      protocol:
        grpc:
          endpoint: 0.0.0.0:4317
storage:
  trace:
    backend: local
    local:
      path: /tmp/tempo/blocks
grafana-datasource.yaml: 自動為 Grafana 配置 Tempo 資料來源。
apiVersion: 1
datasources:
  - name: Tempo
    type: tempo
    access: proxy
    url: http://tempo:3200
    isDefault: true
docker-compose.ymlversion: '3.8'
services:
  app:
    build: ./app
    ports:
      - "8000:8000"
    environment:
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otel-collector.yaml"]
    volumes:
      - ./otel-collector.yaml:/etc/otel-collector.yaml
    ports:
      - "4317:4317" # OTLP gRPC
      - "4318:4318" # OTLP HTTP
    depends_on:
      - tempo
  tempo:
    image: grafana/tempo:latest
    command: ["-config.file=/etc/tempo.yaml"]
    volumes:
      - ./tempo.yaml:/etc/tempo.yaml
      - ./tempo-data:/tmp/tempo
    ports:
      - "3200:3200"
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - ./grafana-datasource.yaml:/etc/grafana/provisioning/datasources/datasources.yaml
    depends_on:
      - tempo
別忘了在 app 目錄下建立一個簡單的 Dockerfile
# app/Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
day24 目錄下執行 docker-compose up --build。curl http://localhost:8000/
curl http://localhost:8000/items/123
http://localhost:3000。Explore。Tempo。Search 分頁下的 Service Name 下拉選單中,你應該能看到 fastapi-demo-app。點擊 Run query,你就能看到剛剛產生的 Trace 列表!點擊任一個 Trace,你就可以看到請求的完整火焰圖 (Flame Graph),清楚地呈現了每個 Span 的耗時與呼叫關係。
今天,我們成功地踏出了分散式追蹤的第一步。我們理解了其核心概念,並親手部署了 Tempo,還使用 OpenTelemetry 檢測了一個 FastAPI 應用。明天,我們將探索更強大的功能:如何在 Grafana 中將日誌與追蹤關聯起來。